/*
 * Copyright (C) 2010 ST-Ericsson AS
 * Author: Erwan Bracq / erwan.bracq@stericsson.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 */

#include <dbus/dbus.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>

#include "mid.h"
#include "mid_ipc.h"
#include "mid_log.h"
#include "mid_error.h"
#include "mid_settings.h"
#include "mid_statemachine.h"

#include <apr_portable.h>

#include <atchannel.h>

#define STRING_SZ 32

static struct mid *loc_mid_info;
static DBusConnection *dbcon = NULL;
static size_t used_dbus_fds = 0;
static size_t unused_dbus_fds = 0;
static DBusWatch *used_watches[DBUS_MAX_WATCHERS];
static DBusWatch *unused_watches[DBUS_MAX_WATCHERS];
static struct apr_pollfd_t used_dbus_fds_tab[DBUS_MAX_WATCHERS];
static struct apr_pollfd_t unused_dbus_fds_tab[DBUS_MAX_WATCHERS];
static char broadcasted_state[STRING_SZ];
static char broadcasted_warn[STRING_SZ];
static apr_pool_t* pool;

#define LOCK_MID_DATA(_res_)	_res_ = apr_thread_mutex_lock(loc_mid_info->mutex);
#define UNLOCK_MID_DATA(_res_)	_res_ = apr_thread_mutex_unlock(loc_mid_info->mutex);

static inline void set_string(const char *src_str, char *dest_str)
{
	int sz = strlen(src_str);
	if (likely(sz < STRING_SZ)) {
		strncpy(dest_str, src_str, sz);
		dest_str[sz] = '\0';
	}
}

static dbus_bool_t add_watch(DBusWatch *watch, void *data)
{
	UNUSED(data);
	short cond = APR_POLLHUP | APR_POLLERR;
	int fd;
	unsigned int flags;
	apr_status_t status;
	dbus_bool_t res;
#ifdef HAVE_ANDROID_OS
	fd = dbus_watch_get_fd(watch);
#else
	fd = dbus_watch_get_unix_fd(watch);
#endif

	MLGD("DBUS: Adding watch for fd %d", fd);

	flags = dbus_watch_get_flags(watch);

	if (flags & DBUS_WATCH_READABLE) {
		cond |= APR_POLLIN;
		MLGD("DBUS: IPC get new dbus watch for READABLE condition");
	}
	if (flags & DBUS_WATCH_WRITABLE) {
		cond |= APR_POLLOUT;
		MLGD("DBUS: IPC get new dbus watch for WRITABLE condition");
	}

	LOCK_MID_DATA(status);

	if (status != APR_SUCCESS) {
		char message[100];
		MLGE("MID: IPC Unable to lock internal mid data mutex: %s",
			apr_strerror(status, message, sizeof(message)));
	}

	res = dbus_watch_get_enabled(watch);
	if (res) {
		if (used_dbus_fds < DBUS_MAX_WATCHERS) {
			MLGD("DBUS: IPC new dbus watch id: %zd is marked USED", used_dbus_fds);
			used_watches[used_dbus_fds] = watch;
			used_dbus_fds_tab[used_dbus_fds].desc_type = APR_POLL_SOCKET;
			apr_os_sock_put(&used_dbus_fds_tab[used_dbus_fds].desc.s, &fd, pool);
			used_dbus_fds_tab[used_dbus_fds].reqevents = cond;
			used_dbus_fds++;
		} else {
			MLGE("DBUS: IPC new dbus watch id: %zd is marked USED. BUT can not be added (watcher overflow)", used_dbus_fds);
			return FALSE;
		}
	} else {
		if (unused_dbus_fds < DBUS_MAX_WATCHERS) {
			MLGD("DBUS: IPC new dbus watch id: %zd is marked UNUSED", unused_dbus_fds);
			unused_watches[unused_dbus_fds] = watch;
			unused_dbus_fds_tab[unused_dbus_fds].desc_type = APR_POLL_SOCKET;
			apr_os_sock_put(&unused_dbus_fds_tab[unused_dbus_fds].desc.s, &fd, pool);
			unused_dbus_fds_tab[unused_dbus_fds].reqevents = cond;
			unused_dbus_fds++;
		} else {
			MLGE("DBUS: IPC new dbus watch id: %zd is marked USED. BUT can not be added (watcher overflow)", unused_dbus_fds);
			return FALSE;
		}
	}

	UNLOCK_MID_DATA(res);
	if (res) {
		res = errno;
		MLGE("MID: IPC Unable to release internal mid data mutex: %s",  strerror(res));
	}

	return TRUE;
}

static void remove_watch(DBusWatch *watch, void *data)
{
	UNUSED(data);
	apr_status_t status;
	size_t i;
	int index, found = 0;

	MLGV("DBUS: IPC Removing dbus watch");

	LOCK_MID_DATA(status);
	if (status) {
		char message[100];
		MLGE("MID: IPC Unable to take internal mid data mutex: %s",
			apr_strerror(status, message, sizeof(message)));
	}

	for (i = 0; i < unused_dbus_fds; i++) {
		if (unused_watches[i] == watch) {
			found = 1;
			index = i;
			break;
		}
	}
	if (!found) {
		MLGD("DBUS: IPC watch %p not found in unused pool, try used pool...", (void*)watch);
		for (i = 0; i < used_dbus_fds; i++) {
			if (used_watches[i] == watch) {
				found = 1;
				index = i;
				break;
			}
		}
		if (!found) {
			MLGE("DBUS: IPC watch %p not found in any pool...", (void*)watch);

			UNLOCK_MID_DATA(status);
			if (status != APR_SUCCESS) {
				char message[100];
				MLGE("MID: IPC Unable to release internal mid data mutex: %s",
					apr_strerror(status, message, sizeof(message)));
			}
			return;
		} else {
			MLGD("DBUS: IPC watch %p found in used pool. Removed", (void*)watch);
			/* shift, TODO use a single memmove */
			for (i = index; i < (used_dbus_fds - 1); i++) {
				used_watches[i] = used_watches[i + 1];
				memcpy(&used_dbus_fds_tab[i], &used_dbus_fds_tab[i + 1], sizeof(used_dbus_fds_tab[i + 1]));
			}
			used_watches[i] = NULL;
			memset(&used_dbus_fds_tab[i], 0, sizeof(used_dbus_fds_tab[i]));
			used_dbus_fds--;
		}
	} else {
		MLGD("DBUS: IPC watch %p found in unused pool. Removed", (void*)watch);
		/* shift, TODO use a single memmove */
		for (i = index; i < (unused_dbus_fds - 1); i++) {
			unused_watches[i] = unused_watches[i + 1];
			memcpy(&unused_dbus_fds_tab[i], &unused_dbus_fds_tab[i + 1], sizeof(unused_dbus_fds_tab[i + 1]));
		}
		unused_watches[i] = NULL;
		memset(&unused_dbus_fds_tab[i], 0, sizeof(unused_dbus_fds_tab[i]));
		unused_dbus_fds--;
	}

	UNLOCK_MID_DATA(status);
	if (status != APR_SUCCESS) {
		char message[100];
		MLGE("MID: IPC Unable to release internal mid data mutex: %s",  apr_strerror(status, message, sizeof(message)));
	}
}

static void toggle_watch(DBusWatch *watch, void *data)
{
	MLGV("DBUS: IPC dbus watch toggle");
	remove_watch(watch, data);
	add_watch(watch, data);
}

/* Received signal handler */
static DBusHandlerResult ipc_request_handler(DBusConnection *dbcon,
	DBusMessage *msg, void *data) {

	apr_status_t status;

	DBusMessageIter args;
	DBusMessage *msg_reply;

	char reply_msg[512];
	char* p_reply_msg = reply_msg;

	UNUSED(data);

	if (dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "Reboot") ||
		dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "PowerOn") ||
		dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "PowerOff") ||
		dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "UploadImage") ||
		dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "Upgrade") ||
		dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "GetUpgradeStatus") ||
		dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "GetState") ||
		dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "GetStateExt") ||
		dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "GetWarn") ||
		dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "GetModemId") ||
#if defined(MOTOROLA_FEATURE)
		dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "CrashDumpOff") ||
		dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "CrashDumpDefault") ||
#endif
		dbus_message_is_method_call(msg,
		DBUS_OBJECT_INTERFACE, "InjectEvent")) {

		if (dbus_message_iter_init(msg, &args)
		    && !dbus_message_is_method_call(msg, DBUS_OBJECT_INTERFACE, "InjectEvent")) {
			MLGE("DBUS: IPC received request has arguments!");
			msg_reply = dbus_message_new_error(msg,
				"MID_IPC_REQUEST_ERROR",
				"Invalid arguments list");
		} else {
			msg_reply = dbus_message_new_method_return(msg);
			if (!msg_reply) {
				MLGE("DBUS: IPC unable to create method"
					" return message");
				return DBUS_HANDLER_RESULT_HANDLED;
			}
			dbus_message_iter_init_append(msg_reply, &args);

			if (dbus_message_is_method_call(msg,
				DBUS_OBJECT_INTERFACE, "Reboot")) {
				MLGD("DBUS: IPC Reboot call received");
				LOCK_MID_DATA(status);
				if (status != APR_SUCCESS) {
					char message[100];
					MLGE("MID: MID Unable to take internal mid data mutex: %s",
						apr_strerror(status, message, sizeof(message)));
				}
				loc_mid_info->ipc_reboot_received = 1;
				SET_FLAG(loc_mid_info->event, MID_IPC_RBT_EVT);
				if (loc_mid_info->config->dbus_disable_modem_reboot) {
					MLGW("DBUS: Reboot disabled (dbus_disable_modem_reboot=true)");
					CLEAR_FLAG(loc_mid_info->event, MID_IPC_RBT_EVT);
				}
				if (loc_mid_info->config->dbus_dump_on_reboot) {
					int res = 0;
					ATResponse *atresponse = NULL;

					MLGW("DBUS: Dump on reboot (dbus_dump_on_reboot=true)");
					/* Dump via AT*EDBGCTRL command (leave 100 ms for response) */
					res = at_send_command("AT*EDBGCTRL=0,100", &atresponse);
					if (res < 0 || atresponse->success == 0) {
						MLGW("AT: >>> Failure on AT*EDBGCTRL=0,100 command.");
					} else {
						CLEAR_FLAG(loc_mid_info->event, MID_IPC_RBT_EVT);
					}
					at_response_free(atresponse);
				}
				UNLOCK_MID_DATA(status);
				if (status != APR_SUCCESS) {
					char message[100];
					MLGE("MID: MID Unable to release internal mid data mutex: %s",
						apr_strerror(status, message, sizeof(message)));
				}
				strncpy(p_reply_msg, "OK", strlen("OK"));
				p_reply_msg[strlen("OK")] = '\0';
			}
			if (dbus_message_is_method_call(msg,
				DBUS_OBJECT_INTERFACE, "PowerOn")) {
				MLGD("DBUS: IPC PowerOn call received");
				LOCK_MID_DATA(status);
				if (status != APR_SUCCESS) {
					char message[100];
					MLGE("MID: MID Unable to take internal mid data mutex: %s",
						apr_strerror(status, message, sizeof(message)));
				}
				if (loc_mid_info->state != STATE_OFF) {
					strncpy(p_reply_msg, "REJECTED", strlen("REJECTED"));
					p_reply_msg[strlen("REJECTED")] = '\0';
				} else {
					SET_FLAG(loc_mid_info->event, MID_IPC_RBT_EVT);
					strncpy(p_reply_msg, "OK", strlen("OK"));
					p_reply_msg[strlen("OK")] = '\0';
				}
				UNLOCK_MID_DATA(status);
				if (status != APR_SUCCESS) {
					char message[100];
					MLGE("MID: MID Unable to release internal mid data mutex: %s",
						apr_strerror(status, message, sizeof(message)));
				}
			}
			if (dbus_message_is_method_call(msg,
				DBUS_OBJECT_INTERFACE, "PowerOff")) {
				MLGD("DBUS: IPC PowerOff call received");
				LOCK_MID_DATA(status);
				if (status != APR_SUCCESS) {
					char message[100];
					MLGE("MID: MID Unable to take internal mid data mutex: %s",
						apr_strerror(status, message, sizeof(message)));
				}
				if (loc_mid_info->state != STATE_OFF) {
					SET_FLAG(loc_mid_info->event, MID_IPC_RBT_EVT);
					SET_FLAG(loc_mid_info->event, MID_MODEM_HALT_EVT);
					strncpy(p_reply_msg, "OK", strlen("OK"));
					p_reply_msg[strlen("OK")] = '\0';
				} else {
					strncpy(p_reply_msg, "REJECTED", strlen("REJECTED"));
					p_reply_msg[strlen("REJECTED")] = '\0';
				}
				UNLOCK_MID_DATA(status);
				if (status != APR_SUCCESS) {
					char message[100];
					MLGE("MID: MID Unable to release internal mid data mutex: %s",
						apr_strerror(status, message, sizeof(message)));
				}
			}
			if (dbus_message_is_method_call(msg,
				DBUS_OBJECT_INTERFACE, "GetState")) {
				MLGD("DBUS: IPC GetState received");
				strncpy(p_reply_msg, broadcasted_state, strlen(broadcasted_state));
				p_reply_msg[strlen(broadcasted_state)] = '\0';
			}
			if (dbus_message_is_method_call(msg,
				DBUS_OBJECT_INTERFACE, "GetStateExt")) {
				MLGD("DBUS: IPC GetStateExt received");
				if (loc_mid_info->modem_warm_start == 1 && loc_mid_info->state == STATE_ON) {
					strncpy(p_reply_msg, "warm_on", 7);
					p_reply_msg[7] = '\0';
				} else {
					strncpy(p_reply_msg, broadcasted_state, strlen(broadcasted_state));
					p_reply_msg[strlen(broadcasted_state)] = '\0';
				}
			}
			if (dbus_message_is_method_call(msg,
				DBUS_OBJECT_INTERFACE, "GetModemId")) {
				MLGD("DBUS: IPC GetModemId received");
				strncpy(p_reply_msg, "Modem_ARCH:", strlen("Modem_ARCH:"));
				p_reply_msg[strlen("Modem_ARCH:")] = '\0';
				strncat(p_reply_msg, loc_mid_info->config->modem_arch, strlen(loc_mid_info->config->modem_arch));
				p_reply_msg[strlen("Modem_ARCH:") + strlen(loc_mid_info->config->modem_arch)] = '\0';
				strncat(p_reply_msg, " | Modem_BUILD:", strlen(" | Modem_BUILD:"));
				p_reply_msg[strlen("Modem_ARCH:") + strlen(loc_mid_info->config->modem_arch) +
					strlen(" | Modem_BUILD:")] = '\0';
				strncat(p_reply_msg, loc_mid_info->modem_build_string, strlen(loc_mid_info->modem_build_string));
				p_reply_msg[strlen("Modem_ARCH:") + strlen(loc_mid_info->config->modem_arch) +
					strlen(" | Modem_BUILD:") + strlen(loc_mid_info->modem_build_string)] = '\0';
			}
			if (dbus_message_is_method_call(msg,
				DBUS_OBJECT_INTERFACE, "GetWarn")) {
				MLGD("DBUS: IPC GetWarn received");
				strncpy(p_reply_msg, broadcasted_warn, strlen(broadcasted_warn));
				p_reply_msg[strlen(broadcasted_warn)] = '\0';
			}
#if defined(MOTOROLA_FEATURE)
			if (dbus_message_is_method_call(msg,
				DBUS_OBJECT_INTERFACE, "CrashDumpOff")) {
				MLGD("IPC CrashDumpOff received");
				LOCK_MID_DATA(status);
				if (status != APR_SUCCESS) {
					char message[100];
					MLGE("MID Unable to take internal mid data mutex: %s",
						apr_strerror(status, message, sizeof(message)));
				}
				mot_mid_info.dual_boot_mode = 1;
				UNLOCK_MID_DATA(status);
				strncpy(p_reply_msg, "OK", strlen("OK"));
				p_reply_msg[strlen("OK")] = '\0';
			}
			if (dbus_message_is_method_call(msg,
				DBUS_OBJECT_INTERFACE, "CrashDumpDefault")) {
				MLGD("IPC CrashDumpDefault received");
				LOCK_MID_DATA(status);
				if (status != APR_SUCCESS) {
					char message[100];
					MLGE("MID Unable to take internal mid data mutex: %s",
						apr_strerror(status, message, sizeof(message)));
				}
				mot_mid_info.dual_boot_mode = 0;
				UNLOCK_MID_DATA(status);
				strncpy(p_reply_msg, "OK", strlen("OK"));
				p_reply_msg[strlen("OK")] = '\0';
			}
#endif

			if (dbus_message_is_method_call(msg,
				DBUS_OBJECT_INTERFACE, "InjectEvent")) {
				DBusError error;
				const char* event_name;
				MID_Event_t event;

				dbus_error_init(&error);
				MLGD("DBUS: IPC InjectEvent received");
				if (!dbus_message_get_args(msg, &error, DBUS_TYPE_STRING, &event_name, DBUS_TYPE_INVALID)) {
					// TODO: Handle
				}
				if (mid_statemachine_get_event_by_name(event_name, &event)) {
					LOCK_MID_DATA(status);
					if (status != APR_SUCCESS) {
						char message[100];
						MLGE("MID: MID Unable to take internal mid data mutex: %s",
							apr_strerror(status, message, sizeof(message)));
					}
					SET_FLAG(loc_mid_info->event, event);
					UNLOCK_MID_DATA(status);
					strcpy(reply_msg, "OK");
				} else {
					snprintf(reply_msg, sizeof(reply_msg), "no such event: %s", event_name);

				}
			}

			/* ========== FW Upgrade DBus Handlers ================== */

			if (dbus_message_is_method_call(msg,DBUS_OBJECT_INTERFACE, "UploadImage")) {
				MLGD("DBUS: FWUpgrade: Got %s", dbus_message_get_member(msg));
				if (loc_mid_info->fw_upgr_img_xfer_reply_msg) {
					strncpy(p_reply_msg, "ERROR: Request in progress",
					        strlen("ERROR: Request in progress"));
					p_reply_msg[strlen("ERROR: Request in progress")] = '\0';
					goto exit;
				}
				LOCK_MID_DATA(status);
				loc_mid_info->fw_upgr_img_xfer_reply_msg = msg_reply;
				SET_FLAG(loc_mid_info->event, MID_IPC_FW_UPGR_IMG_XFER_EVT);
				UNLOCK_MID_DATA(status);
				return DBUS_HANDLER_RESULT_HANDLED;
			}

			if (dbus_message_is_method_call(msg,DBUS_OBJECT_INTERFACE, "Upgrade")) {
				MLGD("DBUS: FWUpgrade: Got %s", dbus_message_get_member(msg));
				if (loc_mid_info->fw_upgr_flash_reply_msg) {
					strncpy(p_reply_msg, "ERROR: Request in progress",
					        strlen("ERROR: Request in progress"));
					p_reply_msg[strlen("ERROR: Request in progress")] = '\0';
					goto exit;
				}
				LOCK_MID_DATA(status);
				loc_mid_info->fw_upgr_flash_reply_msg = msg_reply;
				SET_FLAG(loc_mid_info->event, MID_IPC_FW_UPGR_FLASH_EVT);
				UNLOCK_MID_DATA(status);
				return DBUS_HANDLER_RESULT_HANDLED;
			}

			if (dbus_message_is_method_call(msg,DBUS_OBJECT_INTERFACE, "GetUpgradeStatus")) {
				MLGD("DBUS: FWUpgrade: Got %s", dbus_message_get_member(msg));
				if (loc_mid_info->fw_upgr_status_reply_msg) {
					strncpy(p_reply_msg, "ERROR: Request in progress",
					        strlen("ERROR: Request in progress"));
					p_reply_msg[strlen("ERROR: Request in progress")] = '\0';
					goto exit;
				}
				LOCK_MID_DATA(status);
				loc_mid_info->fw_upgr_status_reply_msg = msg_reply;
				SET_FLAG(loc_mid_info->event, MID_IPC_FW_UPGR_STATUS_EVT);
				UNLOCK_MID_DATA(status);
				return DBUS_HANDLER_RESULT_HANDLED;
			}
exit:
			/* ====================================================== */

			if (!dbus_message_iter_append_basic(&args,
				    DBUS_TYPE_STRING,
				    &p_reply_msg)) {
				MLGE("DBUS: IPC unable to create method"
					" return argument");
			}
		}

		if (!dbus_connection_send(dbcon, msg_reply, NULL)) {
			MLGE("DBUS: IPC unable to reply on received RPC");
			dbus_message_unref(msg_reply);
			return DBUS_HANDLER_RESULT_HANDLED;
		};

		dbus_connection_flush(dbcon);
		dbus_message_unref(msg_reply);
		return DBUS_HANDLER_RESULT_HANDLED;
	}
	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
#if defined(MOTOROLA_FEATURE)
// This function is same as ipc_medium_disconnect, but ignores the Errors and completes all cleanup steps
int ipc_medium_cleanup(void)
{
	DBusError err;

	if (dbcon == NULL)
		goto exit;

	/* Initialize the dbus error return value */
	dbus_error_init(&err);

	/* Disconnection clean up */
	dbus_bus_remove_match(dbcon,
		"type='method_call', "
		"interface='" DBUS_OBJECT_INTERFACE "'", &err);
	if (dbus_error_is_set(&err)) {
		MLGE("DBUS: IPC dispatcher failed to remove"
			" match rule %s: %s", err.name, err.message);
		dbus_error_free(&err);
	}
	dbus_connection_remove_filter(dbcon,
		(DBusHandleMessageFunction)ipc_request_handler, NULL);

	/* Flush connection buffer */
	dbus_connection_flush(dbcon);

	/* Deregister from the bus */
	dbus_bus_release_name(dbcon, DBUS_CONNECTION_NAME, &err);
	if (dbus_error_is_set(&err)) {
		MLGE("DBUS: IPC DBUS release error %s: %s", err.name,
			err.message);
		dbus_error_free(&err);
	}
	dbus_connection_unref(dbcon);
	dbus_error_free(&err);
	apr_pool_destroy(pool);
exit:
	MLGD("DBUS: IPC service disconnected");
	return 0;
}
#endif

int ipc_medium_disconnect(void)
{
	DBusError err;

	if (dbcon == NULL)
		goto exit;

	/* Initialize the dbus error return value */
	dbus_error_init(&err);

	/* Disconnection clean up */
	dbus_bus_remove_match(dbcon,
		"type='method_call', "
		"interface='" DBUS_OBJECT_INTERFACE "'", &err);
	if (dbus_error_is_set(&err)) {
		MLGE("DBUS: IPC dispatcher failed to remove"
			" match rule %s: %s", err.name, err.message);
		dbus_error_free(&err);
		return -EMIDIPCDIS;
	}
	dbus_connection_remove_filter(dbcon,
		(DBusHandleMessageFunction)ipc_request_handler, NULL);

	/* Flush connection buffer */
	dbus_connection_flush(dbcon);

	/* Deregister from the bus */
	dbus_bus_release_name(dbcon, DBUS_CONNECTION_NAME, &err);
	if (dbus_error_is_set(&err)) {
		MLGE("DBUS: IPC DBUS release error %s: %s", err.name,
			err.message);
		dbus_error_free(&err);
		return -EMIDIPCDIS;
	}
	dbus_connection_unref(dbcon);
	dbus_error_free(&err);
	apr_pool_destroy(pool);
exit:
	MLGD("DBUS: IPC service disconnected");
	return 0;
}
#if defined(MOTOROLA_FEATURE)
int ipc_medium_connect(struct mid *midinfo, int reconnect)
#else
int ipc_medium_connect(struct mid *midinfo)
#endif
{
	int res;
	DBusError err;
	loc_mid_info = midinfo;

	MLGD("DBUS: Connecting to DBUS");

	apr_pool_create(&pool, NULL);
#if defined(MOTOROLA_FEATURE)
    if(!reconnect)
#endif
    {
	   set_string("unknown", broadcasted_state);
	   set_string("none", broadcasted_warn);
    }
	/* Initialize the dbus error return value */
	dbus_error_init(&err);
	/* Connect to system dbus */
	dbcon = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
	if (!dbcon || dbus_error_is_set(&err)) {
		MLGE("DBUS: IPC DBUS connect error %s: %s", err.name,
			err.message);
		dbus_error_free(&err);
		return -EMIDIPCCON;
	}

	/* Declare MID service */
	res = dbus_bus_request_name(dbcon, DBUS_CONNECTION_NAME,
		DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
	if (dbus_error_is_set(&err)) {
		MLGE("DBUS: IPC service request error %s: %s", err.name,
			err.message);
		dbus_error_free(&err);
		return -EMIDIPCCON;
	}
	if (res != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
		dbus_error_free(&err);
		return -EMIDIPCCON;
	}

	if (!dbus_connection_set_watch_functions(dbcon, add_watch, remove_watch, toggle_watch, NULL, NULL)) {
		MLGE("DBUS: IPC service failed to add watches");
		return -EMIDIPCCON;
	}

	/* Adds a match rules to match messages going through the message bus */
	/* Listen only rpc to DBUS_OBJECT_INTERFACE interface */
	dbus_bus_add_match(dbcon,
	    "type='method_call', interface='" DBUS_OBJECT_INTERFACE "'",
	    &err);
	if (dbus_error_is_set(&err)) {
		MLGE("DBUS: IPC connect failed to add match rule %s: %s",
			err.name, err.message);
		dbus_error_free(&err);
		return -EMIDIPCCON;
	}

	/* Add a message filter to process incoming messages */
	if (!dbus_connection_add_filter(dbcon,
		(DBusHandleMessageFunction)ipc_request_handler, NULL, NULL)) {
		MLGE("DBUS: IPC connect failed to add signal filter");
		dbus_error_free(&err);
		return -EMIDIPCCON;
	};

	dbus_error_free(&err);

	/* Increment the reference count on the DBusConnection */
	dbus_connection_ref(dbcon);

	MLGD("DBUS: IPC service connected");
	MLGD("DBUS: file descriptor usage: #used fds: %zd, #unused fds: %zd", used_dbus_fds, unused_dbus_fds);

	return 0;
}

int ipc_prepare_message(union ipc_msg *msg, enum ipc_msg_type type, ...)
{
	va_list ap;
	va_start(ap, type);
	memset(msg, 0, sizeof(union ipc_msg));
	switch (type) {
	case STATECHANGE:
		msg->type = STATECHANGE;
		msg->msg_statechange.newstate = va_arg(ap, char*);
		msg->msg_statechange.reason = va_arg(ap, char*);
		set_string((const char *)msg->msg_statechange.newstate, broadcasted_state);
		break;
	case FW_UPGR_STATUS:
		msg->type = FW_UPGR_STATUS;
		msg->msg_fw_upgr_status.statusmsg = va_arg(ap, char*);
		break;
	case FW_UPGR_XFER_PROGRESS:
		msg->type = FW_UPGR_XFER_PROGRESS;
		msg->msg_fw_xfer_progress.percent = va_arg(ap, int);
		break;
	case MODEMWARN:
		msg->type = MODEMWARN;
		msg->msg_modemwarn.warnmsg = va_arg(ap, char*);
		set_string((const char *)msg->msg_modemwarn.warnmsg, broadcasted_warn);
		break;
#ifdef MOTOROLA_FEATURE
	case MFARESULT:
		msg->type = MFARESULT;
		msg->msg_mfaresult.result = va_arg(ap, char*);
		break;
#endif
	case NULLMSG:
	default:
		MLGE("DBUS: IPC invalid message type");
		va_end(ap);
		return -EMIDIPCINVMSG;
	}
	va_end(ap);
	return 0;
}

int ipc_send_async_reply(void **msg, const char *status)
{
	DBusError err;
	int res = 0;

	if (!*msg || !status) {
		MLGE("DBUS: send_async_reply received NULL ptr");
		return -EMIDIPCTXMSG;
	}

	DBusMessage *reply_msg = *(DBusMessage **)msg;
	*msg = 0;

	dbus_error_init(&err);

	if (!dbus_message_append_args(reply_msg,
	                              DBUS_TYPE_STRING,
	                              &status,
	                              DBUS_TYPE_INVALID)) {
		MLGE("DBUS: Message args error %s: %s", err.name, err.message);
		res = -EMIDIPCTXMSG;
		goto error_cleanup;
	}

	if (!dbus_connection_send(dbcon, reply_msg, NULL)) {
		MLGE("DBUS: IPC unable to send fw upgrade reply");
		res = -EMIDIPCTXMSG;
		goto error_cleanup;
	}

	dbus_connection_flush(dbcon);

error_cleanup:
	dbus_message_unref(reply_msg);
	dbus_error_free(&err);

	return res;
}

int ipc_send_message(union ipc_msg msg)
{
	DBusError err;
	DBusMessage *dbmsg;
	int res = 0;

	/* Initialize the dbus error return value */
	dbus_error_init(&err);

	switch (msg.type) {
	case STATECHANGE:
		dbmsg = dbus_message_new_signal(DBUS_OBJECT_PATH,
			DBUS_OBJECT_INTERFACE, "StateChange");
		if (dbmsg == NULL) {
			MLGE("DBUS: IPC cannot create message");
			res = -EMIDIPCTXMSG;
			goto error_cleanup1;
		}

		/* No reply wanted */
		dbus_message_set_no_reply(dbmsg, TRUE);

		/* Message arguments */
		if (!dbus_message_append_args(dbmsg,
			    DBUS_TYPE_STRING, &msg.msg_statechange.newstate,
			    DBUS_TYPE_STRING, &msg.msg_statechange.reason,
			    DBUS_TYPE_INVALID)){
			MLGE("DBUS: IPC message args error %s: %s", err.name,
				err.message);
			res =  -EMIDIPCTXMSG;
			goto error_cleanup2;
		}
		MLGD("DBUS: IPC send message type: StateChange, value: %s", msg.msg_statechange.newstate);
		break;
	case FW_UPGR_STATUS:
		dbmsg = dbus_message_new_signal(DBUS_OBJECT_PATH,
			DBUS_OBJECT_INTERFACE, "FirmwareUpgradeStatus");
		if (dbmsg == NULL) {
			MLGE("DBUS: IPC cannot create message");
			res = -EMIDIPCTXMSG;
			goto error_cleanup1;
		}

		/* No reply wanted */
		dbus_message_set_no_reply(dbmsg, TRUE);

		/* Message arguments */
		if (!dbus_message_append_args(dbmsg,
			    DBUS_TYPE_STRING, &msg.msg_fw_upgr_status.statusmsg,
			    DBUS_TYPE_INVALID)){
			MLGE("DBUS: IPC message args error %s: %s", err.name,
				err.message);
			res =  -EMIDIPCTXMSG;
			goto error_cleanup2;
		}
		MLGD("DBUS: IPC send message type: FW_UPGR_STATUS, value: %s", msg.msg_fw_upgr_status.statusmsg);
		break;
	case FW_UPGR_XFER_PROGRESS:
		dbmsg = dbus_message_new_signal(DBUS_OBJECT_PATH,
			DBUS_OBJECT_INTERFACE, "FWImgXferProgress");
		if (dbmsg == NULL) {
			MLGE("DBUS: IPC cannot create message");
			res = -EMIDIPCTXMSG;
			goto error_cleanup1;
		}

		/* No reply wanted */
		dbus_message_set_no_reply(dbmsg, TRUE);

		/* Message arguments */
		if (!dbus_message_append_args(dbmsg,
			    DBUS_TYPE_BYTE, &msg.msg_fw_xfer_progress.percent,
			    DBUS_TYPE_INVALID)){
			MLGE("DBUS: IPC message args error %s: %s", err.name,
				err.message);
			res =  -EMIDIPCTXMSG;
			goto error_cleanup2;
		}
		MLGD("DBUS: IPC send message type: FW_UPGR_XFER_PROGRESS, value: %d", msg.msg_fw_xfer_progress.percent);
		break;
	case MODEMWARN:
		dbmsg = dbus_message_new_signal(DBUS_OBJECT_PATH,
			DBUS_OBJECT_INTERFACE, "ModemWarn");
		if (dbmsg == NULL) {
			MLGE("DBUS: IPC cannot create message");
			res = -EMIDIPCTXMSG;
			goto error_cleanup1;
		}

		/* No reply wanted */
		dbus_message_set_no_reply(dbmsg, TRUE);

		/* Message arguments */
		if (!dbus_message_append_args(dbmsg,
			    DBUS_TYPE_STRING, &msg.msg_modemwarn.warnmsg,
			    DBUS_TYPE_INVALID)){
			MLGE("DBUS: IPC message args error %s: %s", err.name,
				err.message);
			res =  -EMIDIPCTXMSG;
			goto error_cleanup2;
		}
		MLGD("DBUS: IPC send message type: ModemWarn, value: %s", msg.msg_modemwarn.warnmsg);
		break;
#ifdef MOTOROLA_FEATURE
	case MFARESULT:
		dbmsg = dbus_message_new_signal(DBUS_OBJECT_PATH,
			DBUS_OBJECT_INTERFACE, "MfaResult");
		if (dbmsg == NULL) {
			MLGE("IPC cannot create message");
			res = -EMIDIPCTXMSG;
			goto error_cleanup1;
		}

		/* No reply wanted */
		dbus_message_set_no_reply(dbmsg, TRUE);

		/* Message arguments */
		if (!dbus_message_append_args(dbmsg,
			    DBUS_TYPE_STRING, &msg.msg_mfaresult.result,
			    DBUS_TYPE_INVALID)){
			MLGE("IPC message args error %s: %s", err.name,
				err.message);
			res =  -EMIDIPCTXMSG;
			goto error_cleanup2;
		}
		MLGD("IPC send message type: MfaResult, value: %s", msg.msg_mfaresult.result);
		break;
#endif
	case NULLMSG:
	default:
		MLGE("DBUS: IPC invalid message type");
		res = -EMIDIPCINVMSG;
		goto error_cleanup1;
	}

	/* Send the message */
	if (!dbus_connection_send(dbcon, dbmsg, NULL)) {
		MLGE("DBUS: IPC message send error");
		res =  -EMIDIPCTXMSG;
		goto error_cleanup2;
	};

	/* Flush connection buffer */
	dbus_connection_flush(dbcon);

error_cleanup2:
	/* Drop reference count on the message */
	dbus_message_unref(dbmsg);
error_cleanup1:
	dbus_error_free(&err);

	return res;
}

void ipc_notify_for_event(int index, short event)  {
	unsigned int flags = 0;
	if (event & APR_POLLIN)
		flags |= DBUS_WATCH_READABLE;
	if (event & APR_POLLOUT)
		flags |= DBUS_WATCH_WRITABLE;
	if (event & APR_POLLHUP)
		flags |= DBUS_WATCH_HANGUP;
	if (event & APR_POLLERR)
		flags |= DBUS_WATCH_ERROR;

    
	while (!dbus_watch_handle(used_watches[index], flags)) {
		MLGW("DBUS: IPC dbus_watch_handle needs more memory. Spinning");
		sleep(1);
  	}
	MLGD("DBUS: IPC used id: %d  flags %d dbus_watch_handle selected for DBUS operation", index,flags);
}

void ipc_process_event(void)  {
	int res = 0;
	for (;;) {
		res = dbus_connection_dispatch(dbcon);
		switch (res) {
			case DBUS_DISPATCH_COMPLETE:
				return;
			case DBUS_DISPATCH_NEED_MEMORY:
				MLGW("DBUS: IPC dbus_connection_dispatch needs more memory. Spinning");
				sleep(1);
				break;
			case DBUS_DISPATCH_DATA_REMAINS:
				MLGD("DBUS: IPC dispatch: remaining data for DBUS operation. Spinning");
				break;
		}
	}
}

size_t ipc_get_num_fds(void)
{
	return used_dbus_fds;
}

size_t ipc_get_fds(apr_pollfd_t* out, size_t max)
{
	size_t to_copy;
	if (max >= used_dbus_fds)
		to_copy = used_dbus_fds;
	else {
		to_copy = max;
		MLGW("DBUS: Not enough room for all DBUS fd, needed:%zd, available room:%zd.", used_dbus_fds, max);
	}
	memcpy(out, used_dbus_fds_tab, to_copy * sizeof(struct apr_pollfd_t));
	return to_copy;
}
